/*
* All code is only for jiecai 
* All rights reserved
*/
package com.jc.common.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import redis.clients.util.SafeEncoder;

/**
 *
 * @author Kenny
 * created on 2010-12-23
 */
public class RedisUtil {

	/**
	 * This helper method will assume the List<byte[]> being presented is the list returned
	 * from a {@link JRedis} method such as {@link JRedis#smembers(String)}, and that this
	 * list contains the {@link DefaultCodec#encode(Serializable)}ed bytes of the parametric type <code>T</code>.
	 * <p>
	 * Specifically, this method will instantiate an {@link ArrayList} for type T, of equal 
	 * size to the size of bytelist {@link List}.  Then it will iterate over the byte list 
	 * and for each byte[] list item call {@link DefaultCodec#decode(byte[])}.
	 * <p>
	 * <b>Usage example:</b>
	 * <pre><code>
	 * List<byte[]>  memberBytes = redis.smembers("my-object-set");
	 * List<MySerializableClass>  members = decode (memberBytes);
	 * </code></pre>
	 * @param <T>
	 * @param byteList
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static final <T extends Serializable>  
	List<T> decode (List<byte[]> byteList) {
		List<T>		objectList = new ArrayList<T>(byteList.size());
		for (byte[] bytes : byteList) {
			if(null != bytes){
				T object = (T) decode(bytes);
				objectList.add ((T) object);
			}
			else{
				objectList.add(null);
			}
		}
		return objectList;
	}
	/**
	 * This helper method will serialize the given serializable object of type T
	 * to a byte[], suitable for use as a value for a redis key, regardless of
	 * the key type.
	 * 
	 * @param <T>
	 * @param obj
	 * @return
	 */
	public static final <T extends Serializable> byte[] encode(T obj) {
		byte[] bytes = null;
		try {
			ByteArrayOutputStream bout = new ByteArrayOutputStream();
			ObjectOutputStream out = new ObjectOutputStream(bout);
			out.writeObject(obj);
			bytes = bout.toByteArray();
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException("Error serializing object" + obj
					+ " => " + e);
		}
		// this for development phase only -- will be removed. (A bit of
		// performance hit.)
		// finally {
		// // test it!
		// try {
		// T decoded = decode(bytes); // we want this compile warning to
		// remember to remove this in future.
		// }
		// catch (Exception e) {
		// e.printStackTrace();
		// System.err.format("error in verifying the decoding of the encoded object %s",
		// obj.getClass().getName());
		// }
		// }
		return bytes;
	}

	/**
	 * This helper method will assume that the byte[] provided are the
	 * serialized bytes obtainable for an instance of type T obtained from
	 * {@link ObjectOutputStream} and subsequently stored as a value for a Redis
	 * key (regardless of key type).
	 * <p>
	 * Specifically, this method will simply do:
	 * 
	 * <pre>
	 * <code>
	 * ObjectInputStream oin = new ObjectInputStream(new ByteArrayInputStream(bytes));
	 * t = (T) oin.readObject();
	 * </code>
	 * </pre>
	 * 
	 * and returning the reference <i>t</i>, and throwing any exceptions
	 * encountered along the way.
	 * <p>
	 * This method is the decoding peer of
	 * {@link DefaultCodec#encode(Serializable)}, and it is assumed (and
	 * certainly recommended) that you use these two methods in tandem.
	 * <p>
	 * Naturally, all caveats, rules, and considerations that generally apply to
	 * {@link Serializable} and the Object Serialization specification apply.
	 * 
	 * @param <T>
	 * @param bytes
	 * @return the instance for <code><b>T</b></code>
	 */
	@SuppressWarnings("unchecked")
	public static final <T extends Serializable> T decode(byte[] bytes) {
		if (bytes == null) {
			return null;
		}
		T t = null;
		Exception thrown = null;
		try {
			ObjectInputStream oin = new ObjectInputStream(
					new ByteArrayInputStream(bytes));
			t = (T) oin.readObject();
		} catch (IOException e) {
			e.printStackTrace();
			thrown = e;
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
			thrown = e;
		} catch (ClassCastException e) {
			e.printStackTrace();
			thrown = e;
		} finally {
			if (null != thrown)
				throw new RuntimeException(
						"Error decoding byte[] data to instantiate java object - "
								+ "data at key may not have been of this type or even an object",
						thrown);
		}
		return t;
	}
	
	/**
	 * Convert a data map to binary bytes known by jedis.
	 */
	public static final <T extends Serializable> byte[][] encode(Map<String, T> map) {
		if (map == null) {
			return null;
		}
		byte[][] mappings = new byte[map.size()*2][];
		int i = 0;
		for (Map.Entry<String, T> entry : map.entrySet()) {
			mappings[i++] = SafeEncoder.encode(entry.getKey());
			mappings[i++] = encode(entry.getValue());
		}
		
		return mappings;
	}
	
}
